/*
 * Copyright 2015-2016 Amazon.com, Inc. or its affiliates.  All Rights Reserved.
 *
 * You may not use this file except in compliance with the terms and conditions set forth
 * in the accompanying LICENSE.TXT file.
 *
 * THESE MATERIALS ARE PROVIDED ON AN "AS IS" BASIS.  AMAZON SPECIFICALLY DISCLAIMS, WITH
 * RESPECT TO THESE MATERIALS, ALL WARRANTIES, EXPRESS, IMPLIED, OR STATUTORY, INCLUDING
 * THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT.
 */

package com.amazon.alexa.voicechrome.internal.views.util.wave;

import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Build;
import android.support.annotation.FloatRange;
import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.animation.AccelerateDecelerateInterpolator;

import com.amazon.alexa.voicechrome.R;
import com.amazon.alexa.voicechrome.VoiceChromeVisuals;

import java.text.DecimalFormat;
import java.util.Random;

/**
 * Implements the AmplitudeView representing the ListeningView
 */
public class AmplitudeView extends View {

    private static final float DEFAULT_SCALE = 1f;
    private static final float MAX_RATIO_CONTRACT = .71f;
    private static final int ANIMATION_DURATION = 60;
    public static final float VARIANCE_MIN = .01f;
    public static final float VARIANCE_MAX = .3f;

    private int viewWidth;
    private int viewHeight;
    private int middleWidth;
    private ObjectAnimator progressAnimator;
    private float currentRatio = 1;
    private DecimalFormat decimalFormat = new DecimalFormat("#.###");
    private long animationDuration;
    private float maxContractionAmplitude;

    private AmplitudePainter amplitudePainter;
    private int barHeight;

    public AmplitudeView(Context context) {
        super(context);
        init();
    }

    public AmplitudeView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public AmplitudeView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    public AmplitudeView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        init();
    }

    private void init() {
        amplitudePainter = new AmplitudePainter(getContext());
        setLayerType(View.LAYER_TYPE_SOFTWARE, null);
        if (isInEditMode()) {
            setAmplitude(0.0f);
        }

        barHeight = getContext().getResources().getDimensionPixelSize(R.dimen.voicechrome_progress_bar_height);

        setMaxContractionAmplitude(MAX_RATIO_CONTRACT);
        setAnimationDuration(ANIMATION_DURATION);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        viewWidth = w;
        viewHeight = h;
        middleWidth = w / 2;
        amplitudePainter.updatePaintShadersForSize(viewWidth, viewHeight);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);
        canvas.drawRect(0, 0, viewWidth, viewHeight, amplitudePainter.amplitudeGlowPaint);
        canvas.drawRect(0, viewHeight-barHeight, viewWidth, viewHeight, amplitudePainter.amplitudePaintBase);
    }

    public void setAmplitude(double rms) {
        updateAmplitude(rms);
    }

    @Override
    protected void onDetachedFromWindow() {
        super.onDetachedFromWindow();
        if (progressAnimator != null) {
            progressAnimator.removeAllUpdateListeners();
            progressAnimator.end();
            progressAnimator.cancel();
            progressAnimator = null;
        }
    }

    /**
     * Animate the amplitudeView with the given amplitude. The view contracts by a ratio of the provided amplitude
     *
     * @param amplitude
     */
    private void updateAmplitude(@FloatRange(from = 0.0, to = 1.0) double amplitude) {
        Log.d("MISS", " updateAmplitude ----- " + (getScaleRatio(amplitude) != formatDecimalPlace(currentRatio)));
        if (getScaleRatio(amplitude) != formatDecimalPlace(currentRatio)) {
            Log.d("MISS", " getScaleRatio -------- " + getScaleRatio(amplitude));
            refreshAmplitude(getScaleRatio(amplitude));
        } else {
            currentRatio = getScaleRatio(amplitude);
            if (progressAnimator != null) {
                progressAnimator.end();
            }
        }
    }

    /**
     * animate the view's scale using the provided ratio
     *
     * @param amplitudeRatio
     */
    private void refreshAmplitude(final float amplitudeRatio) {
        if (progressAnimator != null && progressAnimator.isRunning()) {
            if (amplitudeRatio == 0.0) {
                progressAnimator.end();
            }
            return;
        }
        createAnimatorIfNeeded();

        // If we're expanding the gradient again, then slow down the animation
        if (currentRatio < amplitudeRatio) {
            progressAnimator.setDuration(animationDuration * 2);
        } else {
            progressAnimator.setDuration(animationDuration);
        }
        progressAnimator.setFloatValues(currentRatio, amplitudeRatio);

        if (VoiceChromeVisuals.loggingEnabled) {
            Log.d(AmplitudeView.class.getSimpleName(), "refreshAmplitude from curr " + currentRatio + " to new " + amplitudeRatio);
        }

        if (!progressAnimator.isRunning()) {
            progressAnimator.start();
        }
    }

    private void createAnimatorIfNeeded() {
        if (progressAnimator == null) {
            progressAnimator = new ObjectAnimator();
            progressAnimator.setTarget(this);
            progressAnimator.setPropertyName("scaleX");
            progressAnimator.setDuration(animationDuration);
            progressAnimator.setInterpolator(new AccelerateDecelerateInterpolator());
            progressAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                @Override
                public void onAnimationUpdate(ValueAnimator animation) {
                    currentRatio = (float) animation.getAnimatedValue();
                }
            });
        }
    }

    /**
     * Returns the scaling for the given amplitude. No amplitude scales the view to 1.0 (full size). When the amplitude
     * is greater than the maxContractionAmplitude, then a random reduction is added the the amplitude.
     * This keeps the amplitude animating instead of staying contracted
     *
     * @param amplitude
     * @return a scale for the given amplitude. ie: amplitude 0 = scale 1.0
     */
    private float getScaleRatio(double amplitude) {
        if (amplitude == 0.0) {
            return DEFAULT_SCALE;
        } else {
            return formatDecimalPlace(calculateScaleRatioDifference(amplitude));
        }
    }

    private double calculateScaleRatioDifference(double amplitude) {
        Log.d("MISS", " calculateScaleRatioDifference ------ " + (amplitude > maxContractionAmplitude));
        if (amplitude > maxContractionAmplitude) {
            return getAmplitudeWithOvershotReduction();
        } else {
            Log.d("MISS", " 1f - amplitude ------ " + (1f - amplitude) + " ---- " + amplitude);
            return 1f - amplitude;
        }
    }

    /**
     * @return a float for the scale amount reducing the overshot by some random number between VARIANCE_MIN and VARIANCE_MAX
     */
    private float getAmplitudeWithOvershotReduction() {
        float overShotVariance = VARIANCE_MIN + (VARIANCE_MAX - VARIANCE_MIN) * new Random().nextFloat();
        float reduction = maxContractionAmplitude - overShotVariance;
        return 1f - reduction;
    }

    private float formatDecimalPlace(double value) {
        return Float.valueOf(decimalFormat.format(value));
    }

    public void setAnimationDuration(long animationDuration) {
        this.animationDuration = animationDuration;
        if (progressAnimator != null) {
            if (progressAnimator.isRunning()) {
                progressAnimator.end();
            }
            progressAnimator.setDuration(this.animationDuration);
        }
    }

    public void setMaxContractionAmplitude(float maxContractionAmplitude) {
        this.maxContractionAmplitude = maxContractionAmplitude;
    }

    public long getAnimationDuration() {
        return animationDuration;
    }

    public double getMaxContractionAmplitude() {
        return maxContractionAmplitude;
    }
}
